﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace MoneyWentWhere.Model
{
    /// <summary>
    /// Maintains the ordering of transactions by date.
    /// Listens to date changes and reorders automatically.
    /// </summary>
    public class TransactionList : ObservableCollection<Transaction>
    {
        /// <summary>
        /// Adjust the index based on the date and insert the item.
        /// </summary>
        /// <param name="index"></param>
        /// <param name="item"></param>
        protected override void InsertItem(int index, Transaction item)
        {
            if (item == null) { throw new ArgumentNullException("item"); }

            // Move back while date < previousDate
            while (item.Date < GetTxDate(index - 1)) { --index; }
            // Move forward date > nextDate
            while (item.Date > GetTxDate(index)) { ++index; }

            item.PropertyChanged += Item_PropertyChanged;
            base.InsertItem(index, item);
        }

        protected override void RemoveItem(int index)
        {
            this[index].PropertyChanged -= Item_PropertyChanged;
            base.RemoveItem(index);
        }

        /// <summary>
        /// Listening to date changes and reordering automatically.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Date") { OnItemDateChanged(sender as Transaction); }
        }

        /// <summary>
        /// Figure out the new position based on the new date, and move the item.
        /// Minimal move up or down from current position.
        /// </summary>
        /// <param name="item"></param>
        void OnItemDateChanged(Transaction item)
        {
            if (item == null) { throw new ArgumentNullException("item"); }

            int oldIndex = IndexOf(item);
            if (oldIndex < -1) { throw new InvalidOperationException("Item not in the list"); }

            // Find a new index
            int newIndex = oldIndex;

            // Move back while date < previousDate
            while (item.Date < GetTxDate(newIndex - 1)) { --newIndex; }
            // Move forward date > nextDate
            while (item.Date > GetTxDate(newIndex + 1)) { ++newIndex; }

            MoveItem(oldIndex, newIndex);
        }

        /// <summary>
        /// Move the item and adjust the date to be equal to new next/previous item
        /// (if it's not already between them).
        /// </summary>
        /// <param name="oldIndex"></param>
        /// <param name="newIndex"></param>
        protected override void MoveItem(int oldIndex, int newIndex)
        {
            if (oldIndex == newIndex) { return; }

            // Get prev/next dates at new insertion point
            int index = newIndex;
            if (oldIndex < newIndex) { ++index; } // will be pulling one out, add it in
            DateTime prevDate = GetTxDate(index - 1);
            DateTime nextDate = GetTxDate(index);

            var item = this[oldIndex];

            // Turn off event temporarily, fix date, turn back on
            item.PropertyChanged -= Item_PropertyChanged;
            if (item.Date < prevDate) { item.Date = prevDate; }
            if (item.Date > nextDate) { item.Date = nextDate; }
            item.PropertyChanged += Item_PropertyChanged;

            base.MoveItem(oldIndex, newIndex);
        }

        protected override void SetItem(int index, Transaction item)
        {
            throw new NotImplementedException();

            // TODO: remove old, add new events... adjust date, or change index?
            // Not sure which user scenario this method supports.
            base.SetItem(index, item);
        }

        /// <summary>
        /// Returns the transaction date or MinValue/MaxValue when out of bounds
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        DateTime GetTxDate(int index)
        {
            if (index >= Count) { return DateTime.MaxValue; }
            if (index < 0) { return DateTime.MinValue; }
            return this[index].Date;
        }
    }
}
